package com.geek.gkpush.framework.redis.locker;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.params.SetParams;

import java.util.ArrayList;
import java.util.List;

/**
 * <h1>Redis 锁工具类。</h1><br/>
 *
 * <h1><br/><a href="https://u.geekbang.org/subject/arch3/100614201">Java 业务架构实战营：https://u.geekbang.org/subject/arch3/100614201</a></h1><br/><h1><a href="https://gitee.com/ylimhhmily/BizArchPracticeCamp">Java 业务架构实战营代码地址：https://gitee.com/ylimhhmily/BizArchPracticeCamp</a></h1><br/><h1><a href="https://u.geekbang.org/subject/java4th/1001148?source=app_share">高级 Java 工程体系课 2023 版：https://u.geekbang.org/subject/java4th/1001148?source=app_share</a></h1><br/><h1><a href="https://time.geekbang.org/column/intro/100312101?tab=catalog">Dubbo 源码剖析与实战：https://time.geekbang.org/column/intro/100312101?tab=catalog</a></h1>
 *
 * @author hmilyylimh
 * ^_^
 * @version 0.0.1
 * ^_^
 * @date 2023/9/2
 */
@Slf4j
@Component
public class RedisLocker {

    private static final String UNLOCK_LUA = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    private static final Long UNLOCK_SUCC_CODE = 1L;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * <h2>尝试加锁。</h2>
     *
     * @throws
     * @param: lockKey
     * @param: lockVal
     * @param: expiryTimeMillis
     * @return: boolean：<h2>{@code true}：加锁成功；{@code false}：加锁失败。</h2>
     */
    public boolean tryLock(String lockKey, String lockVal, long expiryTimeMillis) {
        final List<String> keys = new ArrayList<>();
        keys.add(lockKey);
        final List<Object> argv = new ArrayList<>();
        argv.add(lockVal);
        argv.add(expiryTimeMillis);

        String result = (String) redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                Object nativeConnection = connection.getNativeConnection();
                if (nativeConnection instanceof JedisCluster) {
                    SetParams params = SetParams.setParams().px(expiryTimeMillis).nx();
                    return ((JedisCluster) nativeConnection).set(lockKey, lockVal, params);
                } else if (nativeConnection instanceof Jedis) {
                    SetParams params = SetParams.setParams().px(expiryTimeMillis).nx();
                    return ((Jedis) nativeConnection).set(lockKey, lockVal, params);
                }

                return "fail";
            }
        });
        log.info("tryLock result: {}", result);
        return "ok".equalsIgnoreCase(result);
    }

    /**
     * <h2>尝试解锁。</h2>
     *
     * @param: lockKey
     * @param: lockVal
     * @return: boolean：<h2>{@code true}：解锁成功；{@code false}：解锁失败。</h2>
     */
    public boolean tryUnLock(String lockKey, String lockVal) {
        List<String> keys = new ArrayList<>();
        keys.add(lockKey);
        List<String> argv = new ArrayList<>();
        argv.add(lockVal);

        try {
            Object result = redisTemplate.execute(new RedisCallback<Long>() {
                @Override
                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    Object nativeConnection = connection.getNativeConnection();
                    if (nativeConnection instanceof JedisCluster) {
                        return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, argv);
                    } else if (nativeConnection instanceof Jedis) {
                        return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, argv);
                    }

                    return 0L;
                }
            });
            log.info("tryUnLock result: {}", result);
            return UNLOCK_SUCC_CODE.equals(result);
        } catch (Exception e) {
            log.info("tryUnLock error.", e);
            return false;
        }
    }
}